package funtools.kotlin

import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.util.*
import java.util.stream.Collectors
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

/**
 * @author LiangMu
 * @version $Id: ConvertProperties2Yaml.java, v 0.1 2022年09月06日 8:09 下午 LiangMu Exp $$
 */
class Config {
    lateinit var key: String
    var value: String? = null
        set(value) {
            field = if (field == null) value else "$field,$value"
        }
    var children: MutableList<Config> = ArrayList()
}

object ConvertProperties2Yaml {
    private const val PROPERTIES_SUFFIX = ".properties"
    private const val YAML_SUFFIX = ".yaml"
    private const val TAB = "    "
    private const val ONE_SPACE = " "
    private const val ARRAY_VALUE_START_MARK = "  - "
    private var configMap: MutableMap<String, Config> = HashMap()
    private var oldPath: String? = null
    private var newPath: String? = null

    fun convert(path: String) {
        this.build(path)
        this.convert(File(path))
        this.clear()
    }

    private fun clear() {
        this.oldPath = null
        this.newPath = null
        this.configMap.clear()
    }

    private fun convert(file: File) {
        if (!file.exists() || !file.isFile) {
            throw RuntimeException("the path-file is not exist or is not file")
        }
        val scan = Scanner(file)
        try {
            while (scan.hasNextLine()) {
                val line = scan.nextLine()
                analyze(line)
            }
            configMap.forEach { sort(it.value) }
            createFile()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            scan.close()
        }
    }

    private fun createFile() {
        val file = File(newPath)
        if (file.exists()) {
            throw RuntimeException("the file have exist, file path = $newPath")
        }
        file.createNewFile()
        val writer = OutputStreamWriter(FileOutputStream(file))
        try {
            var configList = configMap.values
            configList = configList.stream().sorted { v1: Config, v2: Config ->
                v1.key.compareTo(v2.key)
            }.collect(Collectors.toList())
            configList.forEach { write(it, writer) }
        } catch (e: Exception) {
            file.delete()
            e.printStackTrace()
        } finally {
            writer.close()
        }
    }

    private fun write(config: Config, writer: OutputStreamWriter) {
        write(config, writer, 0)
        writer.write("\n")
    }

    private fun write(config: Config, writer: OutputStreamWriter, intervalLeft: Int) {
        val value = if(config.value == null || config.value!!.isBlank()) "" else parseValue(config.value!!, intervalLeft)
        val line = "${TAB * intervalLeft + config.key}:${value}\n"
        writer.write(line)
        config.children.forEach { write(it, writer, intervalLeft + 1) }
    }


    private fun parseValue(value: String, intervalLeft: Int): String {
        val valueArray = value.split(",")
        if (valueArray.size == 1) return "${ONE_SPACE}${valueArray[0]}"
        val start = TAB * intervalLeft + ARRAY_VALUE_START_MARK
        var result = ""
        valueArray.forEach {
            result += "${start}${it}\n"
        }
        return "${ONE_SPACE}\n${result}"
    }


    private fun sort(config: Config) {
        config.children.sortedBy { it.key }
        config.children.forEach { sort(it) }
    }

    private fun analyze(text: String) {
        if (text.startsWith("#")) return
        val map = text.split('=')
        if (map.size != 2) return
        val configKey = map[0].trim()
        val configKeys = configKey.split(".").toList()
        val key = configKeys[0]
        var curConfig = Optional.ofNullable(configMap[key]).orElseGet {
            val v = Config()
            v.key = key
            configMap[key] = v
            v
        }
        configKeys.drop(0)
        val pattern = "\\S+\\[\\d+]".toRegex()
        for (item in configKeys) {
            val lastIndex = item.indexOfLast { it == '[' }
            val isArray = pattern.matches(item)
            val k = if (isArray) item.substring(0, lastIndex) else item
            val temp = curConfig.children.find { it.key == k }
            if (temp == null) {
                val config = Config()
                config.key = k
                curConfig.children.add(config)
                curConfig = config
            } else {
                curConfig = temp
            }
        }
        curConfig.value = map[1].trim()
    }

    private fun build(path: String) {
        if (path.isBlank() || !path.endsWith(PROPERTIES_SUFFIX)) {
            throw RuntimeException("the path-string format mismatch, please check the path. path = $path")
        }
        oldPath = path
        newPath = path.substring(0, path.length - PROPERTIES_SUFFIX.length) + YAML_SUFFIX
    }
}

private operator fun String.times(intervalLeft: Int): String {
    if (intervalLeft == 0) return ""
    var result = ""
    for (i in 0 until intervalLeft) {
        result += this
    }
    return result
}

fun main() {
    val path = "/Users/liangyuanliu/Desktop/projects/leetcode/src/main/java/funtools/kotlin/data/application.properties"
    ConvertProperties2Yaml.convert(path)
}
